/*
 * Project: Sudoku Explainer
 * Copyright (C) 2006-2007 Nicolas Juillerat
 * 
 * Project: Sudoku Hint Engine for Android
 * Copyright (C) 2010-2013 Emanuel Moecklin
 * 
 * Available under the terms of the Lesser General Public License (LGPL)
 */
package com.onegravity.sudoku.solver.rules.chaining;

import java.util.BitSet;
import java.util.Collection;
import java.util.Collections;
import java.util.Map;

import android.net.Uri;

import com.onegravity.sudoku.solver.HintResource;
import com.onegravity.sudoku.solver.HintType;
import com.onegravity.sudoku.solver.core.Potential;
import com.onegravity.sudoku.solver.data.Cell;
import com.onegravity.sudoku.solver.data.ImmutableCell;
import com.onegravity.sudoku.solver.util.BitSetFactory;
import com.onegravity.sudoku.solver.util.HtmlLoader;
import com.onegravity.sudoku.solver.util.Link;

/**
 * - Forcing X-Chain (Turbot Fish)
 * - Forcing X-Chain
 * - Forcing XY-Chain (Remote Pairs)
 * - Forcing XY-Chain
 * - Forcing Chain
 * Consist of one forcing chain
 */
public class ForcingChainHint extends ChainHint {

	private static final long serialVersionUID = -5834175387854660716L;

	private final Potential target;

	private Type chainType;
	private enum Type {
		FORCING_XCHAIN_TURBOT,
		FORCING_XCHAIN,
		REMOTE_PAIRS,
		FORCING_XYCHAIN,
		FORCING_CHAIN;
	};
	
    // Cache
    private int _complexity = -1;

    public ForcingChainHint(ChainMetaInfo metaInfo, Map<Cell, BitSet> removablePotentials, boolean isYChain, boolean isXChain, Potential target) {
        super(metaInfo, removablePotentials, isYChain, isXChain);
        this.target = target;

        // determine how many potentials are involved in this chain
    	int nrOfPotentialsPerCell = 0;
    	BitSet candidates = new BitSet();
        for (Potential potential:getChain(target)) {
        	if (!potential.cell.equals(target.cell)) {
	        	BitSet cand = potential.cell.getPotentialValues();
	        	candidates.or(cand);
	        	nrOfPotentialsPerCell = Math.max(nrOfPotentialsPerCell, cand.cardinality());
        	}
        }
        int nrOfDifferentPotentials = candidates.cardinality();

        // determine type of chains
        chainType = Type.FORCING_CHAIN;
        if (isXChain && isYChain) {
        	chainType = (nrOfPotentialsPerCell==2) ? Type.FORCING_XYCHAIN : Type.FORCING_CHAIN;
        }
        else {
        	chainType = (isXChain && nrOfDifferentPotentials==2) ? Type.REMOTE_PAIRS :
        		   (isXChain && nrOfDifferentPotentials!=2 && (getAncestorCount(target)==6)) ? Type.FORCING_XCHAIN_TURBOT :
        		   Type.FORCING_XCHAIN;
        }
    }
    
    @Override
    public boolean isWorth() {
    	switch (chainType) {
    	case FORCING_XCHAIN_TURBOT:
    	case FORCING_XCHAIN:
            return mMetaInfo.mConfig.useXForcingChains() && super.isWorth();
    	case REMOTE_PAIRS:
    	case FORCING_XYCHAIN:
            return mMetaInfo.mConfig.useXYForcingChains() && super.isWorth();
    	case FORCING_CHAIN:
            return mMetaInfo.mConfig.useForcingChains() && super.isWorth();
    	}
        return super.isWorth();
    }

    @Override
    public int getFlatViewCount() {
        return 1;
    }

    @Override
    public Collection<Cell> getSelectedCells(HintType type) {
        return Collections.singletonList(target.cell);
    }

    @Override
    public Map<Cell, BitSet> getAddedPotentials(HintType type, int viewNum) {
    	Map<Cell, BitSet> result = null;
    	switch (type) {
    	case SMALL_CLUE:
    		result = Collections.singletonMap(target.cell, BitSetFactory.createOneElement(target.value));
            break;
    	case BIG_CLUE:
            result = getColorPotentials(target, true, true);
            result.put(target.cell, BitSetFactory.createOneElement(target.value));
            break;
    	case HINT:
            result = (viewNum >= getFlatViewCount()) ? super.getNestedAddedPotentials(type, viewNum) : getColorPotentials(target, viewNum==0, true);
    	}
        return result;
    }
    
    @Override
    public Map<Cell, BitSet> getSecondarySelectedPotentials(HintType type, int viewNum) {
        if (viewNum >= getFlatViewCount()) return super.getNestedSecondarySelectedPotentials(type, viewNum);
        Map<Cell, BitSet> result = getColorPotentials(target, viewNum>0, true);
        return (type!=HintType.SMALL_CLUE) ? result : null;
    }

    @Override
    public Map<ImmutableCell, BitSet> getRemovedPotentials(HintType type) {
    	Map<ImmutableCell, BitSet> removed = null;
        if (type==HintType.HINT) {
        	removed = super.getRemovedPotentials(type);
        	if (target.isOn) {
        		// if the target is on it will be set to a value and we don't want
        		// to show the removed potentials of the target on top of the added value
	        	for (ImmutableCell cell:removed.keySet()) {
	        		if (cell.getX()==target.cell.getX() && cell.getY()==target.cell.getY()) {
	        			removed.remove(cell);
	        			break;
	        		}
	        	}
        	}
        }
    	return removed;
    }

    @Override
    public Collection<Link> getLinks(HintType type, int viewNum) {
        if (viewNum >= getFlatViewCount()) return super.getNestedLinks(type, viewNum);
        Potential start = this.target;
        return (type==HintType.HINT) ? getLinks(start) : null;
    }

    @Override
    public int getFlatComplexity() {
        if (_complexity < 0) _complexity = getAncestorCount(target);
        return _complexity;
    }

    @Override
    protected Collection<Potential> getChainsTargets() {
        return Collections.singletonList(this.target);
    }

    @Override
    protected Potential getChainTarget(int viewNum) {
        return this.target;
    }

    @Override
    public int getChainingSortKey() {
    	switch(chainType) {
    	case REMOTE_PAIRS:			return 1;
    	case FORCING_XCHAIN_TURBOT:	return 2;
    	case FORCING_XCHAIN:		return 3;
    	case FORCING_XYCHAIN:		return 4;
    	case FORCING_CHAIN:		
    	default:					return 5;
    	}
    }

    public double getDifficulty() {
    	double lenDiff = getLengthDifficulty();
    	switch(chainType) {
    	case REMOTE_PAIRS:			return 5.0 + lenDiff;
    	case FORCING_XCHAIN_TURBOT:
    	case FORCING_XCHAIN:		return 6.6 + lenDiff;
    	case FORCING_XYCHAIN:		return 6.8 + lenDiff;
    	case FORCING_CHAIN:
    	default:					return 7.0 + lenDiff;
    	}
    }

    @Override
    public Cell getCell(HintType type) {
        if (target.isOn) return target.cell;
        return null;
    }

    @Override
    public int getValue() {
        return target.value;
    }

    @Override
    public String getName() {
    	switch(chainType) {
    	case REMOTE_PAIRS:			return HintResource.REMOTE_PAIRS.getName();
    	case FORCING_XCHAIN_TURBOT:	return HintResource.FORCING_XCHAIN_TURBOT.getName();
    	case FORCING_XCHAIN:		return HintResource.FORCING_XCHAIN.getName();
    	case FORCING_XYCHAIN:		return HintResource.FORCING_XYCHAIN.getName();
    	case FORCING_CHAIN:		
    	default:					return HintResource.FORCING_CHAIN.getName();
    	}
    }

    @Override
    public Uri getLink() {
    	switch(chainType) {
    	case REMOTE_PAIRS:			return HintResource.REMOTE_PAIRS.getUrl();
    	case FORCING_XCHAIN_TURBOT:	return HintResource.FORCING_XCHAIN_TURBOT.getUrl();
    	case FORCING_XCHAIN:		return HintResource.FORCING_XCHAIN.getUrl();
    	case FORCING_XYCHAIN:		return HintResource.FORCING_XYCHAIN.getUrl();
    	case FORCING_CHAIN:		
    	default:					return HintResource.FORCING_CHAIN.getUrl();
    	}
    }

    @Override
    protected Potential getResult() {
        return target;
    }

    @Override
    public String getHintHtml(HintType hintType) {
    	HintResource hintRes = HintResource.FORCING_CHAIN;
    	switch(chainType) {
    	case REMOTE_PAIRS:			hintRes = HintResource.REMOTE_PAIRS; break;
    	case FORCING_XCHAIN_TURBOT:	hintRes = HintResource.FORCING_XCHAIN_TURBOT; break;
    	case FORCING_XCHAIN:		hintRes = HintResource.FORCING_XCHAIN; break;
    	case FORCING_XYCHAIN:		hintRes = HintResource.FORCING_XYCHAIN; break;
    	case FORCING_CHAIN:			hintRes = HintResource.FORCING_CHAIN; break;
    	}
    	String result = HtmlLoader.loadHtml(hintRes.hint(hintType));
        Potential reverse = new Potential(target.cell, target.value, !target.isOn);
        String assumption = reverse.toWeakString();
        String consequence = target.toStrongString();
        String conclusion = target.toWeakString();
        String htmlChain = getHtmlChain(target);
    	// Note that first potential occurs twice in the chain
        result = HtmlLoader.format(result, assumption, consequence, conclusion, htmlChain, "", Cell.toString(target.cell), target.value); //$NON-NLS-1$
        return super.appendNestedChainsDetails(result);
    }
    
    @Override
    public String toString() {
        return getName() + ": " + target.toString() + (target.isOn ? " on" : " off"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
    }

}